package server;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Hashtable;

import packets.Packet;
import packets.client.PublicKeyPair;
import packets.server.Certificate;
import packets.server.UserList;

import encription.CertificateRevocationList;


/**
 * a Class that contains the information needed for the server
 * including available user IDs to distribute , all of the existing users and their details
 * , all of the logged in users and their IP/port , and the Certificate Revocation List (CRP)
 */
public class SharedStuff {
	private Hashtable<InetSocketAddress, Integer> loggedIn; //key = ip , value = username
	private Hashtable<Integer ,User> allUsers;
	private CertificateRevocationList _crl;
	private int idGenerator;

	/**
	 * Creates an instance of {@link SharedStuff}
	 */
	public SharedStuff(){
		loggedIn = new Hashtable<InetSocketAddress, Integer>();
		allUsers = new Hashtable<Integer, User>();
		_crl = new CertificateRevocationList();
	}

	/**
	 * method to retrieve all of the current users and their names
	 * @return a {@link UserList} that contains all of the existing users
	 */
	public UserList getAllUsers(){
		synchronized (allUsers){
			int i=0;
			Integer[] ids = new Integer[allUsers.size()];
			String[] names = new String[allUsers.size()];
			for(Integer id : allUsers.keySet()){
				ids[i] = id;
				names[i] = allUsers.get(id).getName();
				i++;
			}
			return new UserList(names, ids);
		}
	}

	/**
	 * method to retrieve a logged in user's IP address
	 * @param id the ID of the user
	 * @return the IP address/port of the requested user , or null if he is not logged in
	 */
	public InetSocketAddress getip(int id){
		InetSocketAddress res = null;
		synchronized (loggedIn) {
			for(InetSocketAddress i : loggedIn.keySet()){
				if (loggedIn.get(i).equals(id)){
					res = i;
					break;
				}
			}
		}
		return res;
	}

	/**
	 * method to retrieve all of the current online users and their names
	 * @return a {@link UserList} that contains all of the logged in users
	 */
	public UserList getOnline() {
		synchronized (loggedIn) {
			synchronized (allUsers){
				int i=0;
				Integer[] ids = new Integer[loggedIn.size()];
				String[] names = new String[loggedIn.size()];
				for(Integer id : loggedIn.values()){
					ids[i] = id;
					names[i] = allUsers.get(id).getName();
					i++;
				}
				return new UserList(names, ids);
			}
		}
	}

	/**
	 * a method to retrieve details about a specific user
	 * @param id the ID of the user
	 * @return the details about the user (see {@link User})
	 */
	public User getDetails(int id) {
		synchronized (allUsers) {
			return allUsers.get(id);
		}
	}

	/**
	 * adds a user to the logged-in table , and saves his IP , updates his Certificate if needed
	 * @param id ID of the user
	 * @param password password of the user (encrypted)
	 * @param inetAddress IP address of the user (and port)
	 * @param serverSigningKey key to sign the updated certificate
	 * @return the Packet to return to the user as an answer for his login request , the Packet will contain a {@link Certificate} if the login was successful or an error message if not.
	 */
	public Packet loginUser(int id,  String password, InetSocketAddress inetAddress , PrivateKey serverSigningKey) {
		synchronized (loggedIn) {
			if (allUsers.get(id)!=null){
				if (loggedIn.get(inetAddress)==null){
					if (allUsers.get(id).verifyPassword(password))
						if (loggedIn.put(inetAddress, id)==null){
							Certificate c = getCertificate(id);
							try{
								c.update(inetAddress, serverSigningKey);
								return new Packet(c);
							}catch (Exception e) {
								return new Packet("errorwhileupdatingcertificate");
							}
						}else{
							return new Packet("useralreadylogged");
						} else {
							return new Packet("badpassword");
						}
				} else {
					return new Packet("ipalreadylogged");
				}
			}else{
				return new Packet("invalidusername");
			}
		}
	}

	/**
	 * deletes a user from the logged in table
	 * @param inetAddress the IP address/port of the user that is logging in
	 * @return true if the user was logged in
	 */
	public boolean logoutUser(InetSocketAddress inetAddress) {
		synchronized (loggedIn) {
			return (loggedIn.remove(inetAddress)!=null);
		}
	}

	/**
	 * adds a new user to the users table
	 * @param clientId the user's id assigned by the server
	 * @param name name of the user
	 * @param password password (encrypted)
	 * @param bday user's birthday
	 * @param country user's country
	 * @param city user's city
	 * @param picture user's picture
	 * @param publicKeyPair pair of public keys to encrypt messages for the user and verify messages received from him
	 * @param certificate the user's certificate which was created by the server
	 * @return true if the user did not exist
	 */
	public boolean registerUser(int clientId, String name, String password, String bday, String country, String city , byte[] picture, PublicKeyPair publicKeyPair , Certificate certificate) {
		User user = new User(name , password, bday , country, city, picture, publicKeyPair, certificate);
		synchronized (allUsers) {
			return (allUsers.put(clientId, user)==null);
		}
	}

	/**
	 * method to retrieve a user's public keys
	 * @param id the ID of the user
	 * @return the user's public keys , used to encrypt messages sent to him and verify messages he sends
	 */
	public PublicKeyPair getPublicKeyPair(int id){
		synchronized (allUsers) {
			User user = allUsers.get(id);
			if (user!=null)
				return allUsers.get(id).getPublicKeyPair();
			else
				return null;
		}
	}

	/**
	 * method to retrieve a certain user's {@link Certificate}
	 * @param id the id of the user
	 * @return the {@link Certificate} which the server assigned to this user
	 */
	public Certificate getCertificate(int id){
		synchronized (allUsers) {
			return allUsers.get(id).getCertificate();
		}
	}

	/**
	 * deletes a logged in user from the database (by his ip address) , and enters his public keys to the CRL
	 * @param inetAddress the IP address of the user
	 * @return true if the deletion was succesful
	 */
	public boolean delete(InetSocketAddress inetAddress) {
		synchronized (loggedIn) {
			synchronized (allUsers) {
				Integer id =  loggedIn.get(inetAddress);
				if (id == null){
					return false;
				}else{
					_crl.addPublicKey(allUsers.get(id).getPublicKeyPair());
					loggedIn.remove(inetAddress);
					allUsers.remove(id);
					return true;
				}
			}
		}
	}

	/**
	 * method to retrieve a logged in user's ID by his IP address
	 * @param inetAddress user's IP address
	 * @return the user's ID
	 */
	public Integer getUser(InetAddress inetAddress) {
		return loggedIn.get(inetAddress);
	}

	/**
	 * a method to receive new client IDs to register with the server
	 * @return a new client ID which is not used
	 */
	public int getNewClientId() {
		return idGenerator++;
	}
	
	/**
	 * checks if a key is in the CRL
	 * @param pk the key to check
	 * @return true if the key is not in the CRL
	 */
	public boolean isLegalKey(PublicKey pk){
		return _crl.isLegal(pk);
	}
	/**
	 * checks if 2 keys are not in the CRL (both of them) 
	 * @param pk the keys to check
	 * @return true if both of the keys are not in the CRL
	 */
	public boolean isLegalKey(PublicKeyPair pk){
		return _crl.isLegal(pk.getEncryptionKey()) &&
		_crl.isLegal(pk.getSigkey());
	}
}
